// Package sys
// @Link  https://github.com/bufanyun/hotgo
// @Copyright  Copyright (c) 2025 HotGo CLI
// @Author  Ms <133814250@qq.com>
// @License  https://github.com/bufanyun/hotgo/blob/master/LICENSE
// @AutoGenerate Version 2.15.11
package sys

import (
	"context"
	"encoding/json"
	"fmt"
	"github.com/gogf/gf/v2/errors/gcode"
	"github.com/gogf/gf/v2/os/glog"
	"github.com/gogf/gf/v2/os/gtime"
	"github.com/samber/lo"
	"hotgo/internal/consts"
	"hotgo/internal/dao"
	"hotgo/internal/library/cache"
	"hotgo/internal/library/contexts"
	"hotgo/internal/library/dict"
	"hotgo/internal/library/hgorm"
	"hotgo/internal/library/hgorm/handler"
	"hotgo/internal/model"
	"hotgo/internal/model/entity"
	"hotgo/internal/model/input/form"
	"hotgo/internal/model/input/sysin"
	"hotgo/internal/service"
	"hotgo/utility/convert"
	"hotgo/utility/excel"
	"time"

	"github.com/gogf/gf/v2/database/gdb"
	"github.com/gogf/gf/v2/errors/gerror"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/os/gctx"
	"github.com/gogf/gf/v2/util/gconv"
)

type sSysAuctionLog struct{}

func NewSysAuctionLog() *sSysAuctionLog {
	return &sSysAuctionLog{}
}

func init() {
	service.RegisterSysAuctionLog(NewSysAuctionLog())
	dict.RegisterFunc("auctionLogOption", "竞拍记录选项", service.SysAuctionLog().Option)
}

// Model 竞拍记录ORM模型
func (s *sSysAuctionLog) Model(ctx context.Context, option ...*handler.Option) *gdb.Model {
	return handler.Model(dao.AuctionLog.Ctx(ctx), option...)
}

// List 获取竞拍记录列表
func (s *sSysAuctionLog) List(ctx context.Context, in *sysin.AuctionLogListInp) (list []*sysin.AuctionLogListModel, totalCount int, err error) {
	mod := s.Model(ctx)

	// 字段过滤
	mod = mod.FieldsPrefix(dao.AuctionLog.Table(), sysin.AuctionLogListModel{})
	mod = mod.Fields(hgorm.JoinFields(ctx, sysin.AuctionLogListModel{}, &dao.Product, "product"))

	// 关联表字段
	mod = mod.LeftJoinOnFields(dao.Product.Table(), dao.AuctionLog.Columns().ProductId, "=", dao.Product.Columns().Id)

	// 查询竞拍活动
	if in.AuctionId > 0 {
		mod = mod.Where(dao.AuctionLog.Columns().AuctionId, in.AuctionId)
	}

	// 查询产品
	if in.ProductId > 0 {
		mod = mod.Where(dao.AuctionLog.Columns().ProductId, in.ProductId)
	}

	if in.Date != nil {
		mod = mod.Where(dao.AuctionLog.Columns().Date, in.Date.Layout(time.DateOnly))
	}

	if in.Status != 0 {
		mod = mod.Where(dao.AuctionLog.Columns().Status, in.Status)
	}

	// 分页
	mod = mod.Page(in.Page, in.PerPage)

	// 排序
	mod = mod.OrderDesc(dao.AuctionLog.Table() + "." + dao.AuctionLog.Columns().Id)

	// 查询数据
	if err = mod.ScanAndCount(&list, &totalCount, false); err != nil {
		err = gerror.Wrap(err, "获取竞拍记录列表失败，请稍后重试！")
		return
	}
	return
}

// Export 导出竞拍记录
func (s *sSysAuctionLog) Export(ctx context.Context, in *sysin.AuctionLogListInp) (err error) {
	list, totalCount, err := s.List(ctx, in)
	if err != nil {
		return
	}

	// 字段的排序是依据tags的字段顺序，如果你不想使用默认的排序方式，可以直接定义 tags = []string{"字段名称", "字段名称2", ...}
	tags, err := convert.GetEntityDescTags(sysin.AuctionLogExportModel{})
	if err != nil {
		return
	}

	var (
		fileName  = "导出竞拍记录-" + gctx.CtxId(ctx)
		sheetName = fmt.Sprintf("索引条件共%v行,共%v页,当前导出是第%v页,本页共%v行", totalCount, form.CalPageCount(totalCount, in.PerPage), in.Page, len(list))
		exports   []sysin.AuctionLogExportModel
	)

	if err = gconv.Scan(list, &exports); err != nil {
		return
	}

	err = excel.ExportByStructs(ctx, tags, exports, fileName, sheetName)
	return
}

// Edit 修改/新增竞拍记录
func (s *sSysAuctionLog) Edit(ctx context.Context, in *sysin.AuctionLogEditInp) (err error) {
	return g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) (err error) {

		// 修改
		if in.Id > 0 {
			if _, err = s.Model(ctx).
				Fields(sysin.AuctionLogUpdateFields{}).
				WherePri(in.Id).Data(in).Update(); err != nil {
				err = gerror.Wrap(err, "修改竞拍记录失败，请稍后重试！")
			}
			return
		}

		// 新增
		if _, err = s.Model(ctx, &handler.Option{FilterAuth: false}).
			Fields(sysin.AuctionLogInsertFields{}).
			Data(in).OmitEmptyData().Insert(); err != nil {
			err = gerror.Wrap(err, "新增竞拍记录失败，请稍后重试！")
		}
		return
	})
}

// Delete 删除竞拍记录
func (s *sSysAuctionLog) Delete(ctx context.Context, in *sysin.AuctionLogDeleteInp) (err error) {

	if _, err = s.Model(ctx).WherePri(in.Id).Unscoped().Delete(); err != nil {
		err = gerror.Wrap(err, "删除竞拍记录失败，请稍后重试！")
		return
	}
	return
}

// View 获取竞拍记录指定信息
func (s *sSysAuctionLog) View(ctx context.Context, in *sysin.AuctionLogViewInp) (res *sysin.AuctionLogViewModel, err error) {
	if err = s.Model(ctx).WherePri(in.Id).Scan(&res); err != nil {
		err = gerror.Wrap(err, "获取竞拍记录信息，请稍后重试！")
		return
	}
	return
}

// Bid 竞拍
func (this *sSysAuctionLog) Bid(ctx context.Context, in *sysin.BidInp) (res *sysin.BidModel, err error) {
	user := contexts.GetUser(ctx)
	res = &sysin.BidModel{
		UserID:              user.Id,
		AdminMemberRealName: user.RealName,
		AdminMemberUsername: user.Username,
		AdminMemberAvatar:   user.Avatar,
		BidAmount:           in.BidAmount,
		BidTime:             gtime.Now(),
	}
	tx, err := g.DB().Begin(ctx)
	if err != nil {
		return nil, err
	}
	defer func() {
		if err != nil {
			_ = tx.Rollback()
			return
		}
		err = tx.Commit()
	}()
	log, err := this.View(ctx, &sysin.AuctionLogViewInp{Id: in.Id})
	if err != nil {
		return nil, gerror.New("竞拍不存在")
	}
	mapper, err := service.SysAuctionProductMapper().View(ctx, &sysin.AuctionProductMapperViewInp{Id: int64(log.MapperId)})
	if err != nil {
		return nil, gerror.New("拍品不存在")
	}
	// 验证竞拍
	{
		if mapper.BasePrice > in.BidAmount {
			return nil, gerror.New("竞拍价格不能低于最低竞拍价格")
		}
		if in.BidAmount < log.FinalPrice {
			return nil, gerror.New("竞拍价格不能低于当前最高价")
		}
		if in.BidAmount-log.FinalPrice < mapper.MinBidPrice {
			return nil, gerror.New("当前竞拍最少加价" + gconv.String(mapper.MinBidPrice))
		}
		if log.StartTime.After(gtime.Now().FormatTo("H:i:s")) {
			return nil, gerror.New("拍卖未开始")
		}
		if log.EndTime.Before(gtime.Now().FormatTo("H:i:s")) {
			return nil, gerror.New("拍卖已结束")
		}
	}
	var (
		bidLog               []sysin.BidModel
		numberOfParticipants int
		uniArr               []sysin.BidModel
	)
	if log.BidLog != nil {
		if err = json.Unmarshal(log.BidLog.MustToJson(), &bidLog); err != nil {
			return nil, err
		}
		uniArr = lo.UniqBy(bidLog, func(item sysin.BidModel) int64 {
			return item.UserID
		})
		numberOfParticipants = len(uniArr)
	} else {
		numberOfParticipants = 1
	}

	bidLog = append([]sysin.BidModel{*res}, bidLog...)
	if _, err = this.Model(ctx).WherePri(in.Id).Data(g.Map{
		dao.AuctionLog.Columns().BidLog:               bidLog,
		dao.AuctionLog.Columns().NumberOfParticipants: numberOfParticipants,
		dao.AuctionLog.Columns().FinalPrice:           in.BidAmount,
		dao.AuctionLog.Columns().LastUserId:           user.Id,
	}).Update(); err != nil {
		return nil, gerror.Wrap(err, "竞拍失败，请稍后重试！")
	}
	if err = service.SysAuctionBidHistory().Edit(ctx, &sysin.AuctionBidHistoryEditInp{
		AuctionBidHistory: entity.AuctionBidHistory{
			UserId:    user.Id,
			LogId:     uint64(in.Id),
			BidAmount: in.BidAmount,
		},
	}); err != nil {
		return nil, gerror.Wrap(err, "竞拍失败，请稍后重试！")
	}
	return
}

// Appointment 预约
func (this *sSysAuctionLog) Appointment(ctx context.Context, in *sysin.AppointmentInp) (res *sysin.AppointmentModel, err error) {
	user := contexts.GetUser(ctx)
	var (
		//m   entity.AuctionLogUserMapper
		log entity.AuctionLog
	)
	// 验证

	if err = service.SysAuctionLog().Model(ctx).WherePri(in.LogID).Scan(&log); err != nil {
		err = gerror.Wrap(err, "预约失败，请稍后重试！")
		return nil, err
	}
	if log.Date.Before(gtime.Now().LayoutNew(time.DateOnly)) {
		err = gerror.New("竞拍已过期")
		return nil, err
	}
	if log.StartTime.Before(gtime.Now().LayoutNew(time.TimeOnly)) {
		err = gerror.New("竞拍已开始")
		return nil, err
	}

	{
		value, cerr := cache.Instance().Get(ctx, fmt.Sprintf("%s:%d", consts.CacheAuctionAppointment, log.Id))
		if cerr != nil {
			glog.Errorf(ctx, "获取缓存失败：%v", cerr)
		}
		dList := value.Int64s()
		if lo.Contains(dList, user.Id) {
			err = gerror.New("已预约,请勿重复预约")
			return nil, err
		}
		dList = append(dList, user.Id)
		// 预约成功，缓存
		if err = cache.Instance().Set(ctx, fmt.Sprintf("%s:%d", consts.CacheAuctionAppointment, log.Id), dList, 24*3*time.Hour); err != nil {
			glog.Errorf(ctx, "缓存失败：%v", err)
		}

		uv, ucerr := cache.Instance().Get(ctx, fmt.Sprintf("%s:%d", consts.CacheAuctionUserAppointment, user.Id))
		if ucerr != nil {
			glog.Errorf(ctx, "获取缓存失败：%v", ucerr)
		}
		uList := uv.Uint64s()
		uList = append(uList, log.Id)
		if err = cache.Instance().Set(ctx, fmt.Sprintf("%s:%d", consts.CacheAuctionUserAppointment, user.Id), uList, 24*3*time.Hour); err != nil {
			glog.Errorf(ctx, "缓存失败：%v", err)
			return
		}
	}
	return &sysin.AppointmentModel{}, nil
}

func (this *sSysAuctionLog) EndNow(ctx context.Context, in *sysin.AuctionLogViewInp) (res *sysin.AuctionLogViewModel, err error) {
	log, err := this.View(ctx, in)
	if err != nil {
		return
	}
	mapper, err := service.SysAuctionProductMapper().View(ctx, &sysin.AuctionProductMapperViewInp{Id: int64(log.MapperId)})
	if err != nil {
		return
	}
	if mapper.EndTime.Before(gtime.Now()) {
		return nil, gerror.New("拍卖已结束")
	}

	if _, err = this.Model(ctx).WherePri(in.Id).Data(g.Map{
		dao.AuctionLog.Columns().EndTime: time.Now(),
	}).Update(); err != nil {
		return nil, gerror.Wrap(err, "结束竞拍失败，请稍后重试！")
	}
	res = log
	return
}

func (this *sSysAuctionLog) Option(ctx context.Context) (opts []*model.Option, err error) {
	var models []*entity.AuctionLog
	if err = this.Model(ctx).Fields(dao.AuctionLog.Columns().Id, fmt.Sprintf("%s-%s", dao.AuctionLog.Columns().StartTime, dao.AuctionLog.Columns().EndTime)).
		OrderDesc(dao.AuctionLog.Columns().Id).Scan(&models); err != nil {
		err = gerror.Wrap(err, "获取竞拍记录选项失败！")
		return
	}
	gtime.Now().Weekday()
	return
}

// ApiList 对客户端的列表接口
func (this *sSysAuctionLog) ApiList(ctx context.Context, in *sysin.AuctionApiLogListInp) (res []*sysin.AuctionLogListModel, totalCount int, err error) {
	mod := this.Model(ctx)
	user := contexts.GetUser(ctx)
	// 字段过滤
	mod = mod.FieldsPrefix(dao.AuctionLog.Table(), sysin.AuctionLogListModel{})
	mod = mod.Fields(hgorm.JoinFields(ctx, sysin.AuctionLogListModel{}, &dao.Product, "product"))
	mod = mod.Fields(hgorm.JoinFields(ctx, sysin.AuctionLogListModel{}, &dao.AuctionProductMapper, "m"))

	// 关联表字段
	mod = mod.LeftJoinOnFields(dao.Product.Table(), dao.AuctionLog.Columns().ProductId, "=", dao.Product.Columns().Id)
	mod = mod.LeftJoinOnFields(dao.AuctionProductMapper.Table(), dao.AuctionLog.Columns().MapperId, "=", dao.AuctionProductMapper.Columns().Id)

	//if in.Date != nil {
	//	mod = mod.Where(dao.AuctionLog.Columns().Date, in.Date.Layout(time.DateOnly))
	//}
	//mod = mod.Wheref("(TIMEDIFF(`hg_auction_log`.`start_time`, ?) <= '03:00:00' AND `hg_auction_log`.`status` = ?) OR (`hg_auction_log`.`status` = ?) OR (TIMEDIFF(`hg_auction_log`.`end_time`, ?) <= '-00:10:00' AND `hg_auction_log`.`status` = ?)", time.Now().Format(time.TimeOnly), consts.AuctionLogWaitStart, consts.AuctionLogWorking, time.Now().Format(time.TimeOnly), consts.AuctionLogEnd)

	if len(in.Status) != 0 {

		mod = mod.WhereIn(dao.AuctionLog.Columns().Status, in.Status)
	}
	// 筛选已预约的(点了预约按钮的)
	if in.IsAppointment {
		if user == nil {
			err = gerror.NewCode(gcode.CodeNotAuthorized, "请先登录")
			return
		}
		logIds, lerr := cache.Instance().Get(ctx, fmt.Sprintf("%s:%d", consts.CacheAuctionUserAppointment, user.Id))
		if lerr != nil {
			glog.Errorf(ctx, "获取缓存失败：%v", lerr)
			return
		}
		mod = mod.WhereIn("id", logIds.Uint64s())
	}
	// 筛选已参与的(付了积分的)
	if in.IsJoin {
		if user == nil {
			err = gerror.NewCode(gcode.CodeNotAuthorized, "请先登录")
			return
		}
		mod = mod.Where("JSON_LENGTH(`hg_auction_log`.`bid_log`) > 0")
		mod = mod.Wheref("JSON_CONTAINS(JSON_EXTRACT(`hg_auction_log`.`bid_log`, '$[*].userId'), CAST(? AS JSON))", user.Id)
	} else {
		mod = mod.Where(dao.AuctionLog.Columns().Date, gtime.Now().Layout(time.DateOnly))
	}

	mod = mod.Page(in.Page, in.PerPage)

	// 排序
	mod = mod.Order(fmt.Sprintf("FIELD(`hg_auction_log`.`status`, %d, %d, %d)", consts.AuctionLogWorking, consts.AuctionLogWaitStart, consts.AuctionLogEnd))
	mod = mod.OrderDesc(dao.AuctionLog.Table() + "." + dao.AuctionLog.Columns().StartTime)
	now := gtime.Now()

	if err = mod.ScanAndCount(&res, &totalCount, false); err != nil {
		err = gerror.Wrap(err, "获取竞拍记录列表失败，请稍后重试！")
		return
	}
	for i, log := range res {
		if user != nil {
			userIds, err := cache.Instance().Get(ctx, fmt.Sprintf("%s:%d", consts.CacheAuctionAppointment, log.Id))
			if err != nil {
				glog.Errorf(ctx, "获取缓存失败：%v", err)
				continue
			}
			if lo.Contains(userIds.Int64s(), user.Id) {
				res[i].IsAppointment = true
			}
		}
		res[i].ServerTime = now
		res[i].StartTime = log.Date.Layout(time.DateOnly) + " " + log.StartTime
		res[i].EndTime = log.Date.Layout(time.DateOnly) + " " + log.EndTime
		if log.BidLog != nil && len(log.BidLog.Interfaces()) > 0 {
			res[i].FinalUserID, _ = log.BidLog.Array()[len(log.BidLog.Array())-1].(map[string]interface{})["userId"].(json.Number).Int64()
			res[i].FinalUserName = log.BidLog.Array()[len(log.BidLog.Array())-1].(map[string]interface{})["adminMemberUsername"].(string)
		}
		if user != nil && log.Status == consts.AuctionLogEnd && log.FinalUserID == user.Id {
			res[i].IsSuccess = true
		}
	}
	return
}

// ApiView 对客户端的详情接口
func (this *sSysAuctionLog) ApiView(ctx context.Context, in *sysin.AuctionLogViewInp) (res *sysin.AuctionLogViewModel, err error) {
	mod := this.Model(ctx)
	user := contexts.GetUser(ctx)
	// 字段过滤
	mod = mod.FieldsPrefix(dao.AuctionLog.Table(), sysin.AuctionLogViewModel{})
	mod = mod.Fields(hgorm.JoinFields(ctx, sysin.AuctionLogViewModel{}, &dao.Product, "product"))
	mod = mod.Fields(hgorm.JoinFields(ctx, sysin.AuctionLogViewModel{}, &dao.AuctionProductMapper, "m"))

	// 关联表字段
	mod = mod.LeftJoinOnFields(dao.Product.Table(), dao.AuctionLog.Columns().ProductId, "=", dao.Product.Columns().Id)
	mod = mod.LeftJoinOnFields(dao.AuctionProductMapper.Table(), dao.AuctionLog.Columns().MapperId, "=", dao.AuctionProductMapper.Columns().Id)

	if err = mod.WherePri(in.Id).Scan(&res); err != nil {
		err = gerror.Wrap(err, "获取竞拍记录信息，请稍后重试！")
		return
	}
	res.StartTime = gtime.NewFromStrLayout(fmt.Sprintf("%s %s", res.Date.Layout(time.DateOnly), res.StartTime), time.DateTime)
	res.EndTime = gtime.NewFromStrLayout(fmt.Sprintf("%s %s", res.Date.Layout(time.DateOnly), res.EndTime), time.DateTime)
	if res.BidLog != nil {
		var bidLog []sysin.BidModel
		if err = json.Unmarshal(res.BidLog.MustToJson(), &bidLog); err != nil {
			return nil, err
		}
		if len(bidLog) > 0 {
			res.FinalUserID = bidLog[len(bidLog)-1].UserID
			res.FinalUserName = bidLog[len(bidLog)-1].AdminMemberUsername
		}
		if user != nil {
			_, idx, exist := lo.FindIndexOf(bidLog, func(item sysin.BidModel) bool {
				return item.UserID == user.Id
			})
			if exist {
				res.Rank = idx + 1
			}
			if res.FinalUserID == user.Id && res.Status == consts.AuctionLogEnd {
				res.IsSuccess = true
			}
			if lo.ContainsBy(bidLog, func(item sysin.BidModel) bool {
				return item.UserID == user.Id
			}) {
				res.IsJoin = true
			}
		}

	}
	res.ServerTime = gtime.Now()

	return
}
